<!--表单组件,插槽中的表单组件必须使用FormItem目录中的，多个子组件在同一行时，每个子组件默认占1份即相同宽度，可通过copies设置所占列的份数调整宽度-->
<template>
  <a-form
      ref="aform"
      :form="aform"
      :labelAlign="labelAlign"
      :layout="layout"
      :selfUpdate="true"
      :class="{'c-percentage-layout':percentageLayout}"
  >
    <slot></slot>
  </a-form>
</template>

<script>
export default {
  name: 'AntForm',
  data() {
    return {
      // 将所有的带Vdecorator属性的vue对象以键值对的形式存储起来
      formObj: {},
      // 将所有的带Vdecorator属性的vue对象以数组的形式存储起来
      domArr: [],
      aform: this.$form.createForm(this),
      // 边间距
      edgeSpacing: 32,
      // 表单标签宽度存放
      formLabelWidthArr: []
    }
  },
  props: {
    /**
     * 是否禁用表单
     */
    disabled: {
      type: Boolean,
      default: false
    },
    /**
     * label 标签的文本对齐方式 'left' | 'right'
     */
    labelAlign: {
      type: String,
      default: 'right'
    },
    /**
     * 是否开启自动百分比布局，启用会产生短暂组件宽度调整动画，建议在页面表单使用，不建议在弹窗表单中使用，否则每次打开弹窗都会有延迟动画
     */
    percentageLayout: {
      type: Boolean,
      default: false
    },
    /**
     * 百分比布局表单总份数
     */
    totalCopies: {
      type: Number,
      default: null
    },
    /**
     * 表单布局 'horizontal'|'vertical'|'inline'
     */
    layout: {
      type: String,
      default: 'inline'
    }
  },
  watch: {
    disabled(val) {
      this.setDisabled(this.disabled)
    }
  },
  created() {
    // 扩展endWith功能
    String.prototype.endWith = function(endStr) {
      let d = this.length - endStr.length
      return d >= 0 && this.lastIndexOf(endStr) === d
    }
  },
  mounted() {
    const _this = this
    //1.第一层 监听：宽度变化，来自动调整表单
    const rootRo = new ResizeObserver(entries => {
      _this.$nextTick(() => {
        _this.autoFixFormLabelWidth()
      })
    })
    rootRo.observe(this.$el)
    //2.第二层 监听：添加或删除子节点
    const observer = new MutationObserver((mutationsList, observer) => {
      // 在每次 DOM 变化时触发的回调函数
      for (const mutation of mutationsList) {
        if (mutation.type === 'childList') {
          // 子节点列表变化（添加或删除子节点）
          _this.autoFixFormLabelWidth()
        }
      }
    })
    //配置 MutationObserver
    const config = { childList: true, subtree: true }
    //开始观察目标节点
    observer.observe(this.$el, config)
  },
  beforeDestroy() {
  },
  methods: {
    /**
     * 校验表单项是否为空，不传参数则全部校验。传1个参数则是回调函数。传2个参数则第1个是需要验证的字段，第2个是回调函数。
     * fieldNames 以逗号分隔的字符串，需要校验的字段
     * callback 回调函数
     * 返回 true 验证通过 false 验证失败
     */
    validateFields() {
      const _this = this
      let fieldNames = ''
      let callback = function() {
      }
      // 判断是否有两个参数
      if (arguments.length === 2) {
        fieldNames = arguments[0] || ''
        callback = arguments[1] || function() {
        }
      } else if (arguments.length === 1 && arguments[0]) {
        if (typeof arguments[0] === 'string') {
          fieldNames = arguments[0]
        } else if (typeof arguments[0] === 'function') {
          callback = arguments[0]
        }
      }
      //开始验证
      let arr = []
      let flag = false
      if (fieldNames) {
        arr = fieldNames.split(',')
      }
      //验证指定字段
      if (arr.length > 0) {
        this.aform.validateFields(arr, (errors, values) => {
          if (!errors) {
            flag = true
          }
          if (!flag) {
            const _message = errors[Object.keys(errors)[0]].errors[0].message
            if (_message && _message.indexOf('required') < 0) {
              _this.$message.warning(_message)
            } else {
              _this.$message.warning(_this.formObj[Object.keys(errors)[0]].cplaceholder + '！')
            }
          }
          callback(!errors)
        })
      } else {
        //验证所有字段
        this.aform.validateFields((errors, values) => {
          if (!errors) {
            flag = true
          }
          if (!flag) {
            const _message = errors[Object.keys(errors)[0]].errors[0].message
            if (_message && _message.indexOf('required') < 0) {
              _this.$message.warning(_message)
            } else {
              _this.$message.warning(_this.formObj[Object.keys(errors)[0]].cplaceholder + '！')
            }
          }
          callback(!errors)
        })
      }
      return flag
    },
    /**
     * 设置表单元素的值
     * @param recordPara 一个json，键是表单字段，值是表单值
     */
    setFieldsValue(recordPara) {
      this.getChildren(this)
      let record = {}
      for (let key in recordPara) {
        let vComp = this.formObj[key]
        if (vComp) {
          // 如果组件有转换器
          if (vComp.beforeSetValue) {
            let tempRecord = vComp.beforeSetValue(recordPara)
            Object.assign(record, tempRecord)
          } else {
            record[key] = recordPara[key]
          }
        }
      }
      this.aform.setFieldsValue(record)
    },
    /**
     * 清空重置表单的值
     */
    resetForm() {
      this.aform.resetFields()
      // 判断是否有自己的清空方法，有则调用
      this.getChildren(this)
      for (let key in this.formObj) {
        let vComp = this.formObj[key]
        if (vComp.resetField) {
          vComp.resetField()
        }
      }
    },
    /**
     * 设置表单元素是否可禁用，优先级比表单组件的禁用高
     * disabled true false
     * filedStr 需要设置的字段,多个字段用逗号分隔,不传则设置所有字段
     */
    setDisabled(disabled, filedStr) {
      let arr = []
      if (filedStr) {
        arr = filedStr.split(',')
      }
      // 数组为空则全部控件设置
      if (arr.length === 0) {
        for (let key in this.formObj) {
          let vComp = this.formObj[key]
          vComp.cdisabled = disabled
        }
      } else {
        for (let i = 0; i < arr.length; i++) {
          let vComp = this.formObj[arr[i]]
          if (vComp) {
            vComp.cdisabled = disabled
          }
        }
      }
    },
    /**
     * 获取表单中多个字段的值
     * @param keyArr 数组，需要被获取的字段 不传入参数，则获取全部组件的值
     * @returns {{}} 键为字段，值为字段值
     */
    getFieldsValue(keyArr) {
      this.getChildren(this)
      let recordPara = this.aform.getFieldsValue()
      // 部分类型字段需要特殊处理，调用组件的beforeGetValue方法
      let record = {}
      for (let key in this.formObj) {
        let vComp = this.formObj[key]
        if (vComp) {
          // 如果组件有转换器
          if (vComp.beforeGetValue) {
            let tempRecord = vComp.beforeGetValue(recordPara)
            Object.assign(record, tempRecord)
          } else {
            record[key] = recordPara[key]
          }
        }
      }
      // 如果指定了需要获取的字段，则只获取需要的字段
      let reRecord = {}
      if (keyArr) {
        for (let i = 0; i < keyArr.length; i++) {
          let key = keyArr[i]
          reRecord[key] = record[key]
        }
      } else {
        reRecord = record
      }
      return reRecord
    },
    /**
     * 获取表单中单个字段的值
     * @param key 字符串，需要被获取值得字段
     * @returns {*} 字符串
     */
    getFieldValue(key) {
      let keyArr = []
      keyArr.push(key)
      let record = this.getFieldsValue(keyArr)
      return record[key]
    },
    /**
     * 将所有的带Vdecorator属性的vue对象以键值对的形式存储起来
     * @param vComp
     * @param flag
     * @param isChild
     */
    getChildren(vComp, flag, isChild) {
      if (!vComp) {
        return
      }
      if (!vComp.$vnode) {
        return
      }
      let tag = vComp.$vnode.tag
      if (isChild && tag.endWith('-AntForm')) {
        return
      }
      let vDecorator = vComp.vDecorator
      if (vDecorator && vDecorator.length > 0 && tag.indexOf('-AntForm') > -1) {
        this.formObj[vDecorator[0]] = vComp
        // 判断dom display!=none且未被搜集过 - 加入domArr (自愿进行自动布局的组件)
        if (flag && vComp.$el.offsetParent && !this.domArr.find(el => el === vComp) && vComp.autoLayoutByForm) {
          this.domArr.push(vComp)
        }
      }
      let arr = vComp.$children
      for (let i = 0; i < arr.length; i++) {
        this.getChildren(arr[i], flag, true)
      }
    },
    /**
     * 自动对齐label位置，每次重新计算
     */
    autoFixFormLabelWidth() {
      // [[第一列],[第二列]...]
      const arr = []
      this.domArr = []
      this.formLabelWidthArr = []
      // 1.获取当前表单下 - 所有需要对齐的form组件
      this.getChildren(this, true)
      if (this.domArr.length === 0) {
        return
      }
      const domArr = []
      Array.from(new Set(this.domArr.map(el => el.$el.getBoundingClientRect().top))).forEach((el, index) => {
        domArr[index] = this.domArr.filter(item => item.$el.getBoundingClientRect().top === el).sort((a, b) => {
          return a.$el.getBoundingClientRect().left - b.$el.getBoundingClientRect().left
        })
      })
      // 2.1.判断是否开启百分比布局
      let oneCopiesWidth = null
      if (this.percentageLayout) {
        // 2.1.1.获取最大分区份数 - 若有配置，则取配置值，无值，从dom中去最大值
        const totalCopies = this.totalCopies ? this.totalCopies : Math.max(...domArr.map(el => el.length))
        oneCopiesWidth = (this.$el.getBoundingClientRect().width - this.edgeSpacing * totalCopies) / totalCopies
      }
      // 2.2.计算规则 - 通过判断当前元素距离顶部间距 与 上一个元素边距作比较 - 第一个dom默认第一列第一个
      let num = 0
      let topDistance
      domArr.flat().forEach((el, index) => {
        const rect = el.$el.getBoundingClientRect()
        if (index > 0 && rect.top === topDistance) {
          num++
        } else if (index === 0 || rect.top > topDistance) {
          num = 0
        }
        arr[num] = arr[num] ? arr[num] : []
        arr[num].push(el)
        // 重置上一节点顶部距离
        topDistance = rect.top

        // 百分比份数
        if (this.percentageLayout && el.copies > 1) {
          num += el.copies - 1
        }
      })
      if (this.layout === 'vertical') {
        // 3.垂直布局 - label宽度不计算，输入框宽度不计算label宽度
        arr.forEach(el => {
          el.forEach(item => {
            // 百分比布局 组件占比
            if (this.percentageLayout && oneCopiesWidth) {
              item.percentageLayoutWidth = oneCopiesWidth * item.copies + (item.copies - 1) * this.edgeSpacing
            }
          })
        })
      } else {
        // 3.根据计算结果，几个数组代表有几列数据 - 来获取对应列的最大宽度
        arr.forEach((el, index) => {
          let maxLabelWidth = 0
          el.forEach(item => {
            // 3.2.获取该列的最大label宽度
            const labels = item.$el.getElementsByTagName('label')
            if (labels && item.label) {
              // 首先让各自的label等于原始宽度 - 在比较出最大宽度
              const domWidth = labels[0].offsetWidth
              labels[0].parentNode.style.width = domWidth + 'px'
              if (maxLabelWidth < domWidth) {
                maxLabelWidth = domWidth
              }
            }
          })
          el.forEach(item => {
            // 设置label宽度
            const labels = item.$el.getElementsByTagName('label')
            if (labels && item.label) {
              labels[0].parentNode.style.width = maxLabelWidth + 'px'
            }
            // 百分比布局 组件占比
            if (this.percentageLayout && oneCopiesWidth) {
              item.percentageLayoutWidth = oneCopiesWidth * item.copies - maxLabelWidth + (item.copies - 1) * this.edgeSpacing
            }
          })
          // 表单标签宽度存储
          this.formLabelWidthArr[index] = maxLabelWidth
        })
      }
      // 回调
      this.$emit('autoFixFormLabelWidthSuccess')
    },
  }
}
</script>

<style lang="less" scoped>
::v-deep .ant-form-item-label label {
  display: inline-block;
  white-space: normal;
  line-height: normal;
}

::v-deep .ant-form-item {
  width: auto;
  margin: 6px 0px;
}

::v-deep .ant-form-item-with-help {
  margin-bottom: 0px;
}

::v-deep .ant-form-item-required::before {
  margin-right: 0px;
}

::v-deep .ant-form-explain {
  display: none !important;
}

::v-deep .ant-col-null {
  display: inline-block !important;
}

::v-deep input:disabled, textarea:disabled {
  opacity: 1;
  -webkit-text-fill-color: rgba(0, 0, 0, 0.85);
}

::v-deep .ant-input[disabled] {
  opacity: 1;
  -webkit-text-fill-color: rgba(0, 0, 0, 0.85);
}

::v-deep .ant-select-selection-selected-value {
  opacity: 1;
  -webkit-text-fill-color: rgba(0, 0, 0, 0.85);
}

::v-deep .ant-form-item-label > label::after {
  content: '';
}
</style>
